module nade.shape;

import std.math;

import derelict.opengl.gl;

import nade.vector;
import nade.color;


class Shape {
	struct Vertex {
		float	x, y;
		float	u, v;
	}

	struct IndexList {
		uint mode;
		uint texture;
		uint[] indices;
	}

	Shape dup()
	{
		auto r = new Shape();
		r._vertices = _vertices.dup;
		r._indexLists = _indexLists.dup;

		return r;
	}

	void render()
	{
		render(Color.WHITE);
	}
	void render(const Color color)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);

		glVertexPointer(2, GL_FLOAT, Vertex.sizeof, &_vertices[0].x);
		glTexCoordPointer(2, GL_FLOAT, Vertex.sizeof, &_vertices[0].u);

		glEnable(GL_TEXTURE_2D);

		color.render();
		foreach(indexList; _indexLists)
		{
			glBindTexture(GL_TEXTURE_2D, indexList.texture);
			glDrawElements(indexList.mode, indexList.indices.length, GL_UNSIGNED_INT, indexList.indices.ptr);
		}

		glDisable(GL_TEXTURE_2D);

		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glDisableClientState(GL_VERTEX_ARRAY);
	}

	void renderSolid(const Color color)
	{
		glEnableClientState(GL_VERTEX_ARRAY);

		glVertexPointer(2, GL_FLOAT, Vertex.sizeof, &_vertices[0].x);
		color.render();

		foreach(indexList; _indexLists)
			glDrawElements(indexList.mode, indexList.indices.length, GL_UNSIGNED_INT, indexList.indices.ptr);

		glDisableClientState(GL_VERTEX_ARRAY);
	}

	void renderWire(const Color color, real size)
	{
		glLineWidth(size);
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

		renderSolid(color);

		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glLineWidth(1);
	}

	Shape translated(const Vector2 rhs) const
	{
		auto r = dup;
		r.translate(rhs);
		return r;
	}
	void translate(const Vector2 rhs)
	{
		const x = rhs.x;
		const y = rhs.y;

		foreach(ref vertex; _vertices)
		{
			vertex.x += x;
			vertex.y += y;
		}
	}

	Shape scaled(const Vector2 rhs) const
	{
		auto r = dup;
		r.scale(rhs);
		return r;
	}
	void scale(const Vector2 rhs)
	{
		const x = rhs.x;
		const y = rhs.y;

		foreach(ref vertex; _vertices)
		{
			vertex.x *= x;
			vertex.y *= y;
		}
	}

	Shape opCat(Shape rhs) const
	{
		auto r = dup;
		r ~= rhs;
		return r;
	}
	Shape opCatAssign(Shape rhs)
	{
		uint offset = _vertices.length;
		_vertices ~= rhs._vertices;

		foreach(indexList; rhs._indexLists)
		{
			auto  nindexList = IndexList(indexList.mode);
			foreach(index; indexList.indices) nindexList.indices ~= index + offset;
			_indexLists ~= nindexList;
		}

		return this;
	}

	uint addVertex(real x, real y, real u, real v)
	{
		_vertices ~= Vertex(x, y, u, v);
		return _vertices.length - 1;
	}
	uint addVertex(const Vector2 position, real u, real v)
	{
		return addVertex(position.x, position.y, u, v);
	}
	uint addVertex(real x, real y, const Vector2 texCoord)
	{
		return addVertex(x, y, texCoord.x, texCoord.y);
	}
	uint addVertex(const Vector2 position, const Vector2 texCoord)
	{
		return addVertex(position.x, position.y, texCoord.x, texCoord.y);
	}

	uint addIndexList(uint type, uint texture, uint[] indices ...)
	{
		_indexLists ~= IndexList(type, texture, indices.dup);

		return _indexLists.length - 1;
	}

	protected {
		IndexList[]		_indexLists;
		Vertex[]		_vertices;
	}
}


class Rectangle : Shape {
	this(real w, real h, uint texture, const Vector2 texScale)
	{
		const w2 = w*0.5;
		const h2 = h*0.5;

		addVertex(-w2, -h2, -w2*texScale.x, -h2*texScale.y);
		addVertex(-w2, h2, -w2*texScale.x, h2*texScale.y);
		addVertex(w2, -h2, w2*texScale.x, -h2*texScale.y);
		addVertex(w2, h2, w2*texScale.x, h2*texScale.y);

		addIndexList(GL_QUADS, texture, 0, 1, 2, 3);
	}
}


class Ellipse : Shape {
	this(real w, real h, int res, uint texture, const Vector2 texScale)
	{
		const w2 = w*0.5;
		const h2 = h*0.5;

		real a = 0;
		const d = PI*2/res;
		for(int i = 0;  i < res;  a += d, ++i)
		{
			addVertex(-sin(a)*w2, cos(a)*h2, -sin(a)*w2*texScale.x, cos(a)*h2*texScale.y);
		}

		uint[] strip;
		int i = 0;
		int j = res - 1;
		while(i < j)
		{
			strip ~= i;
			strip ~= j;

			++i;
			--j;
		}

		if(i == j) strip ~= i;

		addIndexList(GL_TRIANGLE_STRIP, texture, strip);
	}
}


class Circle : Ellipse {
	this(real r, int res, uint texture, const Vector2 texScale)
	{
		super(r*2, r*2, res, texture, texScale);
	}
	this(real r, uint texture, const Vector2 texScale)
	{
		super(r*2, r*2, cast(int)(r*100), texture, texScale);
	}
}


class Ring : Shape {
	this(real ri, real ro, int res, uint texture, const Vector2 texScale)
	{
		uint[] strip;

		real a = 0;
		const d = PI*2/res;
		for(int i = 0;  i <= res;  a += d, ++i)
		{
			strip ~= addVertex(-sin(a)*ri, cos(a)*ri, -sin(a)*ri*texScale.x, cos(a)*ri*texScale.y);
			strip ~= addVertex(-sin(a)*ri, cos(a)*ri, -sin(a)*ri*texScale.x, cos(a)*ri*texScale.y);
		}

		addIndexList(GL_TRIANGLE_STRIP, texture, strip);
	}
}


class Lathe : Shape {
	this(uint texture, const Vector2 texScale, Vector2[] points ...)
	{
		uint[] strip;
		foreach(k, point; points)
		{
			addVertex(point, point*texScale);
			addVertex(point.inverseX, point.inverseX*texScale);

			strip ~= k*2;
			strip ~= k*2 + 1;
		}

		addIndexList(GL_TRIANGLE_STRIP, texture, strip);
	}
}
